package com.demo.onvif.utils;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.net.URLDecoder;
import com.demo.onvif.domain.OnvifEntity;

import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ONVIFDeviceDiscoveryFIN {
    private static final String regexScope = "<\\S+:Scopes>(.*?)<\\S+:Scopes>";
    private static final String regexHardware = "onvif:\\/\\/www\\.onvif\\.org\\/name\\/(\\S+)(?=\\s*)";
    private static final Pattern hardwarePattern = Pattern.compile(regexHardware, Pattern.MULTILINE);
    private static final Pattern scopePattern = Pattern.compile(regexScope, Pattern.MULTILINE);
    // Following constants are related to Discovery process
    private static final int WS_DISCOVERY_TIMEOUT = 300;
    private static final int WS_DISCOVERY_PORT = 3702;
    private static final String WS_DISCOVERY_ADDRESS_IPv4 = "239.255.255.250";

    // note that the probe below MUST be given a unique urn:uuid. Devices will NOT reply if the urn:uuid is not unique!
    private static final String WS_DISCOVERY_PROBE_MESSAGE =
            "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">" +
                    "<s:Header>" +
                    "<a:Action s:mustUnderstand=\"1\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</a:Action>" +
                    "<a:MessageID>uuid:17e84964-954f-4186-b38f-a344b49c9a53</a:MessageID>" +
                    "<a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>" +
                    "</a:ReplyTo><a:To s:mustUnderstand=\"1\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To>" +
                    "</s:Header>" +
                    "<s:Body><Probe xmlns=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\">" +
                    "<d:Types xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:dp0=\"http://www.onvif.org/ver10/network/wsdl\">" +
                    "dp0:NetworkVideoTransmitter</d:Types></Probe></s:Body></s:Envelope>";


    private static Map<String, String> getResponsesToProbe(String uuid, String ipAddress) throws IOException {

        final String probe = WS_DISCOVERY_PROBE_MESSAGE.replaceAll("<a:MessageID>urn:uuid:.*</a:MessageID>", "<a:MessageID>urn:uuid:" + uuid + "</a:MessageID>");

        final int port = 55000;
        //DatagramSocket senderAndReceiver = new DatagramSocket(port, InetAddress.getByName("10.9.112.197")); // so you do need to know the address of your network interface to discover devices on...
        MulticastSocket senderAndReceiver = new MulticastSocket(new InetSocketAddress(ipAddress, port));

        senderAndReceiver.setTimeToLive(64); /* 设置TTL */
        senderAndReceiver.setSoTimeout(WS_DISCOVERY_TIMEOUT);

        // send the probe
        DatagramPacket probeMsg = new DatagramPacket(probe.getBytes(), probe.length(), InetAddress.getByName(WS_DISCOVERY_ADDRESS_IPv4), WS_DISCOVERY_PORT);
        senderAndReceiver.send(probeMsg);

        // read in the responses
        TreeMap<String, String> responses = new TreeMap<>();
        while (true) {
            try {
                byte[] receiverBuffer = new byte[8192];
                DatagramPacket receiverPacket = new DatagramPacket(receiverBuffer, receiverBuffer.length); // this is the packet that receive the response in. Get's updated with the next response on each call to .receive()
                senderAndReceiver.receive(receiverPacket);
                String ip = receiverPacket.getAddress().getHostAddress();

                String result = new String(receiverPacket.getData()).trim();
                responses.put(ip, result);
            } catch (SocketTimeoutException e) {
                break;
            }
        }

        // close the socket
        senderAndReceiver.close();
        return responses;

    }

    public static String getHardwareCode(String xml) {
        final Matcher hardwareMatcher = hardwarePattern.matcher(xml);

        if (hardwareMatcher.find()) {
            return URLDecoder.decode(hardwareMatcher.group(1), StandardCharsets.UTF_8);
        }
        return null;
    }

    public static String getScope(String xml) {
        Matcher matcher = scopePattern.matcher(xml);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }

    public static Integer getPort(String xml) {
        int startIndex = xml.indexOf(":XAddrs>") + ":XAddrs>".length();
        // 找到 "</" 的位置
        int endIndex = xml.indexOf("</", startIndex);

        String result2 = xml.substring(startIndex, endIndex);

        //System.out.println(result2);

        String[] s = result2.split(" ");

        String url = s[0];

        int defaultPort = 80;

        // 定义端口号的正则表达式
        Pattern pattern = Pattern.compile(":(\\d+)");
        Matcher matcher = pattern.matcher(url);

        if (matcher.find()) {
            // 如果找到了端口号，则使用找到的端口号
            //System.out.println("端口号为：" + port);
            return Integer.parseInt(matcher.group(1));
        } else {
            // 如果没有找到端口号，则使用默认端口号
            //System.out.println("没有找到端口号，使用默认端口号：" + defaultPort);
            return defaultPort;
        }
    }

    public static List<OnvifEntity> discoverOnvif(String ipAddress) throws IOException {
        final String uuid = UUID.randomUUID().toString();
        Map<String, String> responses = getResponsesToProbe(uuid, ipAddress);
        final Set<Map.Entry<String, String>> entries = responses.entrySet();
        List<OnvifEntity> onvifEntityList = new ArrayList<>();
        for (Map.Entry<String, String> entry : entries) {
            final String v = entry.getValue();
            String scope = getScope(v);
            OnvifEntity onvifEntity = new OnvifEntity();
            onvifEntity.setHost(entry.getKey());
            onvifEntity.setName(getHardwareCode(scope));
            onvifEntity.setPort(getPort(v));
            onvifEntityList.add(onvifEntity);
        }
        return onvifEntityList;
    }
}